/******************************************************************************************
文件名：tcp_server.c
作者：狼狼    版本：V2.0    创建日期：2021.8.2
文件描述：
    TCP服务端会话。
历史修改记录：
1. 2021-8-2:V2.0 狼狼
创建。
2. 2022-5-11:V2.0 狼狼
代码公开。
******************************************************************************************/
//+------------------------------------------------------------------------------------------+
//|                                            前言
//+------------------------------------------------------------------------------------------+
//|      网络应用程序可能需要创建一个或多个TCP服务端，并对来自不同端口的连接执行不同的处理，在
//|  通常情况下，TCP服务端的accept操作是阻塞且随应用程序持续的，因此需要为一个或多个服务端提供
//|  相互独立的处理线程。
//+------------------------------------------------------------------------------------------+
//|                                          文件说明
//+------------------------------------------------------------------------------------------+
//|      程序定义了一个TCP服务端监听套接字结构以存储描述一个TCP-Server套接字最基本的信息，如：
//|  套接字描述符、接受的IP、绑定端口和别名等。每个套接字对象结构中还存放一个连接成功时的回调
//|  函数指针，当新连接被接受，连接的描述符将作为参数传递到该回调函数；若未指定回调函数，则新
//|  连接将被立即关闭。
//|      考虑到上层调用中可能会转移套接字描述对象的存储位置，例如参数传递（这将在栈上建立结构
//|  的拷贝）、局部变量（随函数调用销毁）等，TCP-Server对象结构将被存储在堆上，由创建函数申请
//|  空间并通过输出参数返回指向对象结构体的指针；由销毁函数释放对象结构占用的堆空间内存。如此，
//|  在上层应用中，对于每一个TCP-Server实例，仅需保存指向该套接字对象结构的指针即可。
//|      需要注意的是：程序本身不负责管理申请的堆空间，如果上层应用遗失了对象结构指针，则可能
//|  会造成内存泄露问题。
//+------------------------------------------------------------------------------------------+
//|                                         头文件包含
//+------------------------------------------------------------------------------------------+
/*|*/#include <stdio.h>
/*|*/#include <string.h>
/*|*/#include <stdlib.h>
/*|*/#include <stdint.h>
/*|*/#include <unistd.h>
/*|*/#include <pthread.h>
/*|*/#include <sys/socket.h>
/*|*/#include <netinet/in.h>
/*|*/#include <arpa/inet.h>
//|
/*|*/#include "tcp_server.h"
/*|*/#include "project_debug.h"
//+------------------------------------------------------------------------------------------+
//|  函数名称：tcp_server_process
//|  功能描述：TCP服务端处理线程
//|  参数说明：指向套接字对象结构的指针
//|  返回值说明：无
//|  备注：
//+------------------------------------------------------------------------------------------+
static void *tcp_server_process(void *p)
{
    struct sockaddr_in local_ip_addr;
    struct sockaddr_in remote_ip_addr;
    char local_ip_address[16];
    char remote_ip_address[16];
    
    /* 获取传参 */
    ST_TCP_SERVER *ptcpser_obj = (ST_TCP_SERVER *)p;
    
    /* 设置线程分离 */
    pthread_detach(pthread_self());
    
    /* 接受连接 */
    struct sockaddr_in tcpser_addr;
    tcpser_addr.sin_family      = AF_INET;
    tcpser_addr.sin_addr.s_addr = inet_addr(ptcpser_obj->ip_address);
    tcpser_addr.sin_port        = htons(ptcpser_obj->port);
    
    /* 等待接受连接 */
    int comm_sock = 0;
    socklen_t addr_len = (socklen_t)sizeof(struct sockaddr_in);
    while(1)
    {
        comm_sock = accept(ptcpser_obj->sock, (struct sockaddr *)&tcpser_addr, &addr_len);
        if(comm_sock < 0)
        {
            OUTPUT("[TCPSER-ERROR]TCP-Server[%s] accept failed with %d!\n",ptcpser_obj->name, comm_sock);
            perror("[Error Details]");
            break;
        }
        else
        {
            /* 获取连接信息 */
            getsockname(comm_sock, (struct sockaddr *)&local_ip_addr, &addr_len);
            getpeername(comm_sock, (struct sockaddr *)&remote_ip_addr, &addr_len);
            inet_ntop(AF_INET, &local_ip_addr.sin_addr,  local_ip_address,  sizeof(local_ip_address));
            inet_ntop(AF_INET, &remote_ip_addr.sin_addr, remote_ip_address, sizeof(remote_ip_address));
            OUTPUT("[TCPSER-INFO]New connection! TCP-Server:%s:%d <- Client:%s:%d\n", 
                                                        local_ip_address, 
                                                        ntohs(local_ip_addr.sin_port),
                                                        remote_ip_address,
                                                        ntohs(remote_ip_addr.sin_port));
            
            /* 执行回调 */
            if(ptcpser_obj->connect_handle == NULL)
            {
                close(comm_sock);
                OUTPUT("[TCPSER-DEBUG]There's no handle functuon. TCP Closed!\n");
            }
            else
            {
                ptcpser_obj->connect_handle(comm_sock);
            }
        }
    }
    
    // ----------------------------------------
    // NOTE:当程序正常退出时，通过线程取消的方
    //      式结束本服务端线程，因此不会运行到
    //      此处。当accept等操作出现错误，则线
    //      程直接退出，使用销毁函数时需区分状
    //      态以决定是否需要执行线程取消。
    // ----------------------------------------
    OUTPUT("[TCPSER-DEBUG]TCP-Server[%s] needs to be cancelled.\n", ptcpser_obj->name);
    ptcpser_obj->state = TCPSER_STAT_EXIT;
    pthread_exit(NULL);
}

//+------------------------------------------------------------------------------------------+
//|  函数名称：tcp_server_create
//|  功能描述：初始化TCP服务端对象结构，为其创建TCP服务端线程
//|  参数说明：用于输出套接字对象结构的指针，目的IP，绑定端口，别名和连接回调函数
//|  返回值说明：成功返回TCPSER_SUCCESS，pptcpser_obj指针输出指向对象结构体的空间，失败返回错误码（负值）
//|  备注：
//+------------------------------------------------------------------------------------------+
int tcp_server_create(ST_TCP_SERVER **pptcpser_obj, 
                      const char     ip_address[], 
                      int            port, 
                      const char     name[], 
                      void (*connect_handle)(int sock_comm))
{
    /* 对象结构指针状态检查 */
    if(pptcpser_obj == NULL)
    {
        OUTPUT("[TCPSER-ERROR]Level 2 pointer can't be NULL!\n");
        return TCPSER_ERROR_ILLEGAL_PARAM;
    }
    if(*pptcpser_obj != NULL)
    {
        OUTPUT("[TCPSER-ERROR]Level 1 pointer mast be NULL!\n");
        return TCPSER_ERROR_ILLEGAL_PARAM;
    }
    
    /* 申请套接字对象结构空间 */
    ST_TCP_SERVER *ptmp = (ST_TCP_SERVER *)calloc(1, sizeof(ST_TCP_SERVER));
    if(ptmp == NULL)
    {
        OUTPUT("[TCPSER-ERROR]Malloc[%d] failed!\n", sizeof(ST_TCP_SERVER));
        return TCPSER_ERROR_MALLOC;
    }
    
    /* 参数检查 */
    if(port <= 0)
    {
        OUTPUT("[TCPSER-ERROR]The port number[%d] must be a positive integer!\n", port);
        return TCPSER_ERROR_ILLEGAL_PARAM;
    }
    if((name != NULL) && (strlen(name) > 15))
    {
        OUTPUT("[TCPSER-ERROR]Name is too long![len > 15]\n");
        return TCPSER_ERROR_ILLEGAL_PARAM;
    }
    if(ip_address == NULL)
    {
        OUTPUT("[TCPSER-INFO]The ip address is NULL, use 0.0.0.0 instead!\n");
        strcpy(ptmp->ip_address, "0.0.0.0");
    }
    else
    {
        strncpy(ptmp->ip_address, ip_address, 15);
        ptmp->ip_address[15] = '\0';
    }
    
    /* 更新套接字对象结构 */
    ptmp->port = port;
    if(name != NULL)
    {
        strncpy(ptmp->name, name, 15);
        ptmp->ip_address[15] = '\0';
    }
    else
    {
        strcpy(ptmp->name, "no-name");
    }
    ptmp->connect_handle = connect_handle;
    
    /* 创建套接字 */
    ptmp->sock = socket(AF_INET, SOCK_STREAM, 0);
    if(ptmp->sock < 0)
    {
        OUTPUT("[TCPSER-ERROR]Create socket failed with %d!\n", ptmp->sock);
        perror("[Error Details]");
        return TCPSER_ERROR_SOCKET;
    }
    
    /* 设置地址重用 */
    int tmp = 1;
    setsockopt(ptmp->sock, SOL_SOCKET, SO_REUSEADDR, &tmp, sizeof(tmp));
    
    /* 套接字绑定 & 启动监听 & 创建处理线程 */
    struct sockaddr_in tcpser_addr;
    tcpser_addr.sin_family      = AF_INET;
    tcpser_addr.sin_addr.s_addr = inet_addr(ptmp->ip_address);
    tcpser_addr.sin_port        = htons(ptmp->port);
    
    do
    {
        int ret = 0;
        ret = bind(ptmp->sock, (struct sockaddr *)&tcpser_addr, sizeof(struct sockaddr));
        if(ret < 0)
        {
            OUTPUT("[TCPSER-ERROR]Bind socket to address[%s:%d] failed with %d!\n", ptmp->ip_address, ptmp->port, ret);
            perror("[Error Details]");
            break;
        }
        
        ret = listen(ptmp->sock, 10);
        if(ret < 0)
        {
            OUTPUT("[TCPSER-ERROR]Socket listen failed with %d!\n", ret);
            perror("[Error Details]");
            break;
        }
        
        if(pthread_create(&ptmp->tid, NULL, tcp_server_process, (void *)ptmp) != 0)
        {
            OUTPUT("[TCPSER-ERROR]Create TCP server thread failed!\n");
            break;
        }
        
        ptmp->state = TCPSER_STAT_OK;
        *pptcpser_obj = ptmp;
        
        OUTPUT("[TCPSER-INFO]Create TCP-Server[%s][%s:%d] done.\n", ptmp->name, ptmp->ip_address, ptmp->port);
        
        return TCPSER_SUCCESS;
    }while(0);
    
    
    close(ptmp->sock);
    free(ptmp);
    return TCPSER_ERROR_SOCKET;
}

//+------------------------------------------------------------------------------------------+
//|  函数名称：tcp_server_destroy
//|  功能描述：去初始化TCP服务端对象结构，关闭其服务端线程
//|  参数说明：指向套接字对象结构指针的指针
//|  返回值说明：成功返回TCPSER_SUCCESS，失败返回错误码（负值）
//|  备注：
//+------------------------------------------------------------------------------------------+
int tcp_server_destroy(ST_TCP_SERVER **pptcpser_obj)
{
    /* 对象结构指针状态检查 */
    if(pptcpser_obj == NULL)
    {
        OUTPUT("[TCPSER-ERROR]Level 2 pointer can't be NULL!\n");
        return TCPSER_ERROR_ILLEGAL_PARAM;
    }
    if(*pptcpser_obj == NULL)
    {
        OUTPUT("[TCPSER-ERROR]Level 1 pointer can't be NULL!\n");
        return TCPSER_ERROR_ILLEGAL_PARAM;
    }
    if((*pptcpser_obj)->state != TCPSER_STAT_OK && 
       (*pptcpser_obj)->state != TCPSER_STAT_EXIT)
    {
        OUTPUT("[TCPSER-ERROR]The current state[%d] does not support this operation!\n", (*pptcpser_obj)->state);
        return TCPSER_ERROR_UNEXPECT_STATE;
    }
    
    /* 关闭服务端 */
    if((*pptcpser_obj)->state != TCPSER_STAT_EXIT)
    {
        pthread_cancel((*pptcpser_obj)->tid);
    }
    close((*pptcpser_obj)->sock);
    OUTPUT("[TCPSER-INFO]TCP-Server[%s][%s:%d] closed.\n", (*pptcpser_obj)->name, (*pptcpser_obj)->ip_address, (*pptcpser_obj)->port);
    
    /* 释放套接字对象结构所占堆空间 */
    free(*pptcpser_obj);
    *pptcpser_obj = NULL;
    
    return TCPSER_SUCCESS;
}
